// ======================================================================
// Hell Lua Bus ( Lua Bind) Lib
// 
// Copyright 2013 Hell-Prototypes
//
// http://code.google.com/p/hell-prototypes/
//
// This is free software, licensed under the terms of the GNU General
// Public License as published by the Free Software Foundation.
// ======================================================================

#include <usb.h> /* libusb header */
//#include <unistd.h> /* for geteuid */
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include "serial.h"
#include "define.h"
#include "xsvf.h"

static usb_dev_handle *g_usb_dev; /* the device handle */

#include "common.c"
#include "io_ctrl.c"
#include "spi.c"
#include "i2c.c"
#include "rs232.c"
#include "iap.c"
#include "aes.c"

static aes_context aes_ctx;
/*
 * Lua stack return: 0: succ, <0:fail
 *
 */
static int l_usb_open(lua_State *L)
{
    struct usb_bus *bus;
    struct usb_device *dev;
    printf("*************************************************************\r\n");
    printf("* Hell Lua Bus lib:\r\n");
    printf("* \tVersion V" MODULE_VERSION "\r\n");
    printf("* \tCopyright (c) 2013 Hell Prototypes\r\n");
    printf("* \thttp://www.hellprototypes.com/\r\n");
    printf("* \t" MODULE_TIMESTAMP "\r\n");
    printf("*************************************************************\r\n");

    int vid_pid = get_int_value(L);
    int vid, pid, ret;

    if(vid_pid > 0) {
        vid = (vid_pid >> 16) & 0xFFFF;
        pid = vid_pid & 0xFFFF;
    } else {
        vid = DEFAULT_VID;
        pid = DEFAULT_PID;
    }

    if(g_usb_dev == NULL) {
        usb_init(); /* initialize the library */
        usb_find_busses(); /* find all busses */
        usb_find_devices(); /* find all connected devices */

        for (bus = usb_get_busses(); bus; bus = bus->next) {
            for (dev = bus->devices; dev; dev = dev->next) {
                if (dev->descriptor.idVendor == vid
                        && dev->descriptor.idProduct == pid) {
                    g_usb_dev = usb_open(dev);
                }
            }
        }

        if(g_usb_dev == NULL) {
            lua_pushnumber (L, -ERR_OPEN);
            return 1;
        } else {
            ret = usb_claim_interface(g_usb_dev, 0);
            if (ret < 0) {
                printf("usb_claim_interface error: %d\r\n", ret);
                usb_close(g_usb_dev);
                g_usb_dev = NULL;
                lua_pushnumber (L, -ERR_CLAIM);
                return 1;
            } else {
                //success: claim_interface
            }

        }
    }

	lua_pushnumber (L, NO_ERR);
    return 1; /* number of results */
}

static int l_usb_close(lua_State *L)
{
    if(g_usb_dev != NULL) {
        if(usb_close(g_usb_dev) <0) {
            lua_pushnumber (L, -ERR_CLOSE);
        } else {
            lua_pushnumber (L, NO_ERR);
        }
        g_usb_dev = NULL;
    } else {
        lua_pushnumber (L, NO_ERR);
    }

    return 1;
}


/*
    table[1] : requesttype
    table[2] : request
    table[3] : value
    table[4] : index
    table[5] : size
    table[6] : timeout
*/
static int l_usb_control_msg(lua_State *L)
{
    int msg_param[32];
    char msg_bytes[1024];
    int data_len;
    int requesttype, request, value, index, size, timeout;
    int ret, i;

    data_len = get_int_table_value(L, msg_param, sizeof(msg_param));
    if(data_len == 6) {
        requesttype = msg_param[0];
        request = msg_param[1];
        value = msg_param[2];
        index = msg_param[3];
        size = msg_param[4];
        timeout = msg_param[5];

        if((size > sizeof(msg_bytes)) || (requesttype & (~(0x40|0x80))) != 0) {
            lua_pushnumber (L, -ERR_PARAM);
            return 1;
        }
        
        data_len = get_byte_table_value(L, msg_bytes, sizeof(msg_bytes));
        //if(data_len < 0) {
        //    printf("<Libhlb> : usb_control_msg() command no pass data section\r\n");
        //}

        if(g_usb_dev != NULL) {
            ret =  usb_control_msg(g_usb_dev, requesttype, request, value, index,
                                msg_bytes, size, timeout);
            if(ret < 0) {
                lua_pushnumber (L, -ERR_CTRL_MSG);
            } else {
                if(requesttype & 0x80) {
                    lua_newtable(L);
                    for(i=0; i<ret; i++) {
                        lua_pushinteger(L, (unsigned char)msg_bytes[i] );
                        lua_rawseti (L, -2, i+1);
                    }
                } else {
                    lua_pushnumber (L, NO_ERR);
                }
            }
        } else {
            lua_pushnumber (L, -ERR_NO_OPEN);
        }
    } else {
        lua_pushnumber (L, -ERR_PARAM);
    }

    return 1;
}

//int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
static int l_usb_bulk_read(lua_State *L)
{
    char bytes[1024*64];
    int data_len;
    int endpoint, readlength, timeout;
    int i;

    int table_top = lua_gettop(L);

    if(table_top > 3) {
        lua_pop(L, table_top);
		lua_pushnumber (L, -ERR_PARAM_NUM);
		return 1;
	} else {
        if(table_top != 3) {
            lua_pop(L, table_top);
        	lua_pushnumber (L, -ERR_PARAM_NUM);
            return 1;
        }
    }

    timeout = get_int_value(L);
    if(timeout < 0) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }

    readlength = get_int_value(L);
    if((readlength > sizeof(bytes)) || (readlength < 0)) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }

    endpoint = get_int_value(L);
    if(endpoint < 0) {
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }
    //printf("*** endpoint = %d, readlength = %d, timeout = %d\r\n", endpoint, readlength, timeout);

    if(g_usb_dev != NULL) {
        data_len = usb_bulk_read(g_usb_dev, endpoint, bytes, readlength, timeout);
        if( data_len < 0) {
            printf("<libhlb>: %s\r\n", usb_strerror());
            lua_pushnumber (L, data_len);
        } else {
            lua_newtable(L);
            for(i=0; i<data_len; i++) {
                lua_pushinteger(L, (unsigned char)bytes[i] );
                lua_rawseti (L, -2, i+1);
            }
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }


    return 1;
}

//int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
static int l_usb_bulk_write(lua_State *L)
{
    char bytes[1024*64];
    int  bytes_len, ret;
    int endpoint, timeout;

    int table_top = lua_gettop(L);

    if(table_top > 3) {
        lua_pop(L, table_top);
		lua_pushnumber (L, -ERR_PARAM_NUM);
		return 1;
	} else {
        if(table_top != 3) {
            lua_pop(L, table_top);
        	lua_pushnumber (L, -ERR_PARAM_NUM);
            return 1;
        }
    }

    timeout = get_int_value(L);
    if(timeout < 0) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }
    bytes_len = get_byte_table_value(L, bytes, sizeof(bytes));
    if(bytes_len <= 0) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }

    endpoint = get_int_value(L);
    if(endpoint < 0) {
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }
    //printf("*** endpoint = %d, bytes_len = %d, timeout = %d\r\n", endpoint, bytes_len, timeout);

    if(g_usb_dev != NULL) {
        ret = usb_bulk_write(g_usb_dev, endpoint, bytes, bytes_len, timeout);
        if( ret < 0) {
            printf("<libhlb>: %s\r\n", usb_strerror());
            lua_pushnumber (L, ret);
        } else {
            lua_pushnumber (L, NO_ERR);
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }

    return 1;
}

static int l_icsp_write(lua_State *L)
{
    int bit_len, wr_data;
    int top = lua_gettop(L);

    if(top != 2) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_NUM);
        return 1;
    }

    if(!lua_isnumber(L, -1)) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_TYPE);
        return 1;
    }
    bit_len = lua_tointeger(L, -1);

    if(!lua_isnumber(L, -2)) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_TYPE);
        return 1;
    }
    wr_data = lua_tointeger(L, -2);
    lua_pop(L, lua_gettop(L));//clear stack

    if(bit_len > 8) {
        lua_pushnumber (L, -bit_len);
        return 1;
    }

    if(g_usb_dev != NULL) {
        if(  usb_control_msg(g_usb_dev, 0x40 , USB_ICSP_WR, (bit_len << 8) | wr_data, 0,
                                 NULL, 0, 3000) < 0) {
            lua_pushnumber (L, -ERR_CTRL_MSG);
        } else {
            lua_pushnumber (L, NO_ERR);
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }
    return 1;
}

static int l_icsp_read(lua_State *L)
{
	unsigned char buffer[8];

    if(g_usb_dev != NULL) {
        if(	usb_control_msg(g_usb_dev, 0x40 | 0x80, USB_ICSP_RD, 0x0000, 0x0000,
                                (char *)buffer, 1, 1000 ) < 0) {
            lua_pushnumber (L, -ERR_CTRL_MSG);
        } else {
            lua_pushnumber (L, buffer[0]);
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }

    return 1;
}

static int l_write_one_byte(lua_State *L, int cmd)
{
    int wr_data;
    int top = lua_gettop(L);

    if(top != 1) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_NUM);
        return 1;
    }

    if(!lua_isnumber(L, -1)) {
        lua_pop(L, lua_gettop(L));//clear stack
        lua_pushnumber (L, -ERR_PARAM_TYPE);
        return 1;
    }
    wr_data = lua_tointeger(L, -1);

    if(g_usb_dev != NULL) {
        if(  usb_control_msg(g_usb_dev, 0x40 , cmd, wr_data, 0,
                                 NULL, 0, 3000) < 0) {
            lua_pushnumber (L, -ERR_CTRL_MSG);
        } else {
            lua_pushnumber (L, NO_ERR);
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }
    return 1;
}

static int l_pdi_ctrl(lua_State *L)
{
    return l_write_one_byte(L, USB_PDI_CTRL);
}

static int l_pdi_write(lua_State *L)
{
    return l_write_one_byte(L, USB_PDI_WR);
}

static int l_pdi_read(lua_State *L)
{
	unsigned char buffer[8];

    if(g_usb_dev != NULL) {
        if(	usb_control_msg(g_usb_dev, 0x40 | 0x80, USB_PDI_RD, 0x0000, 0x0000,
                                (char *)buffer, 1, 1000 ) < 0) {
            lua_pushnumber (L, -ERR_CTRL_MSG);
        } else {
            lua_pushnumber (L, buffer[0]);
        }
    } else {
        lua_pushnumber (L, -ERR_NO_OPEN);
    }

    return 1;
}

static int l_aes_set_key(lua_State *L)
{
    unsigned char bytes[64];
    int  nbits, bytes_len;

    int table_top = lua_gettop(L);

    if(table_top != 2) {
        lua_pop(L, table_top);
    	lua_pushnumber (L, -ERR_PARAM_NUM);
        return 1;
    }


    nbits = get_int_value(L);
    if(nbits < 0) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }
    bytes_len = get_byte_table_value(L, (char *)bytes, sizeof(bytes));
    if(bytes_len <= 0) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }

    if(aes_set_key( &aes_ctx, bytes, nbits ) != 0) {
        lua_pushnumber (L, -ERR_PARAM);
    } else {
        lua_pushnumber (L, NO_ERR);
    }
    return 1;
}

static int l_aes_encrypt_decrypt(lua_State *L, int is_encrypt)
{
    unsigned char input[16], output[16];
    int  bytes_len, i;

    int table_top = lua_gettop(L);

    if(table_top != 1) {
        lua_pop(L, table_top);
    	lua_pushnumber (L, -ERR_PARAM_NUM);
        return 1;
    }

    bytes_len = get_byte_table_value(L, (char *)input, sizeof(input));
    if(bytes_len != 16) {
        lua_pop(L, lua_gettop(L));
        lua_pushnumber (L, -ERR_PARAM);
        return 1;
    }

    if(is_encrypt)
        aes_encrypt( &aes_ctx, input, output);
    else
        aes_decrypt( &aes_ctx, input, output);

    lua_newtable(L);
    for(i=0; i<16; i++) {
        lua_pushinteger(L, output[i] );
        lua_rawseti (L, -2, i+1);
    }
    return 1;
}

static int l_aes_encrypt(lua_State *L)
{
    return l_aes_encrypt_decrypt(L, 1);
}
static int l_aes_decrypt(lua_State *L)
{
    return l_aes_encrypt_decrypt(L, 0);
}

static const struct luaL_Reg libhlb [] = {
    { "usb_open",	l_usb_open },		//call: Hell Lua Bus: usb_open(), Hell Logic Sniffer: usb_open(1)
    { "usb_close",	l_usb_close },		//call: usb_close()
    
    { "usb_control_msg", l_usb_control_msg },		//call: usb_control_msg(table[1..6])
    { "usb_bulk_read", l_usb_bulk_read },		    //call: usb_bulk_read(ep, rd_len, timeout)
    { "usb_bulk_write", l_usb_bulk_write },		    //call: usb_bulk_write(ep, wr_table{...}, timeout})

    { "port_oe", 	l_io_port_oe },		//call: port_oe(int oe_data)
    { "port_write",	l_io_write },		//call: port_write(int data)
    { "port_read",	l_io_read },		//call: port_read() -- return int data
    
    { "byte_oe",	l_io_low_8bit_oe },
	{ "byte_write",	l_io_wr_low_8bit },
	{ "byte_read",	l_io_rd_low_8bit },

    { "bit_oe", 	l_io_bit_oe },		//call: bit_oe(int bit_index, int level)
    { "set_bit",	l_io_setbit },		//call: set_bit(int bit_index)
    { "clr_bit",	l_io_clrbit },		//call: clr_bit(int bit_index)
    { "get_bit",	l_io_getbit },		//call: get_bit(int bit_index) -- return: 1: high, 0: low

    { "msdelay",	l_msdelay },		//call: msdelay(int ms_time)
    { "usdelay",	l_usdelay },		//call: usdelay(int us_time)

    { "rs232_open",	l_rs232_open },		//call: rs232_open(table(baudrate, parity))//parity: 0:none, 1:odd, 2:even
    { "rs232_wr",	l_rs232_wr },		//call: rs232_wr(table data)	//24B Max
    { "rs232_rd",	l_rs232_rd },		//call: rs232_rd(int read_len, int timeout) //24B Max
    { "rs232_close",l_rs232_close},		//call: rs232_close()
    
    { "i2c_start",	l_i2c_start },
    { "i2c_stop",	l_i2c_stop },
    { "i2c_write",	l_i2c_write },
    { "i2c_read",	l_i2c_read},
    
    { "spi_init",	l_spi_init },
    { "spi_byte_rw",l_spi_byte_rw},

    { "icsp_write",	l_icsp_write },      //call: icsp_write(int data, int bit_len) //8 Bit Max
    { "icsp_read",	l_icsp_read},        //call: icsp_read()	//return 1 byte

    { "pdi_ctrl",	l_pdi_ctrl },       //call: icsp_ctrl(int ctrl) //1: open, 0: close
    { "pdi_write",	l_pdi_write },      //call: icsp_write(int data) //8 Bit Max
    { "pdi_read",	l_pdi_read},        //call: icsp_read()	//return 1 byte

    { "aes_set_key",l_aes_set_key},     //call: aes_set_key(key(table), nbits)
    { "aes_encrypt",l_aes_encrypt},     //call: aes_encrypt(table)
    { "aes_decrypt",l_aes_decrypt},     //call: aes_encrypt(table)

    { "IAP_open",   l_iap_open},		//call: IAP_open()
    { "IAP_run",    l_iap_run},			//call: IAP_run(table data) //1888B Max
    { "IAP_close",  l_iap_close},		//call: IAP_close()

	{ "fw_ver",  	l_get_fw_version},	//call: fw_ver() //return firmware version number,eg:0x12 is V1.2
	{ "ctrl_12V",  	l_12V_ctrl},	    //call: 12V_ctrl(1) turn on 12V, 12V_ctrl(0) trun off 12V
    
    { "xsvf_process",  	l_xsvf_process},//call: xsvf_process(callback_readByte, callback_readTDOBit, callback_setPort)

    { NULL, NULL } /* sentinel */
};

/* This change was maked to support windows compiling */
#ifdef WINDOWS
__declspec(dllexport) int luaopen_libhlb(lua_State *L) {
#else
int luaopen_libhlb(lua_State *L) {
#endif
    g_usb_dev = NULL;

	g_io_direction_cache = 0;
	g_io_data_cache = 0;

    g_serial_fd.ifd = 0;

    lua_newtable(L);
    luaL_setfuncs(L, libhlb, 0);
/*
    lua_pushstring(L, MODULE_VERSION);
    lua_setfield(L, -2, "_VERSION_");

    lua_pushstring(L, MODULE_TIMESTAMP);
    lua_setfield(L, -2, "_TIMESTAMP_");

    lua_pushstring(L, MODULE_COPYRIGHT);
    lua_setfield(L, -2, "_COPYRIGHT_");
*/
    return 1;
}
